package com.eidos.blogs.openapi.config;

import io.lettuce.core.resource.ClientResources;
import io.lettuce.core.resource.DefaultClientResources;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.api.RedissonReactiveClient;
import org.redisson.api.RedissonRxClient;
import org.redisson.config.ClusterServersConfig;
import org.redisson.config.Config;
import org.redisson.config.SingleServerConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.*;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.data.redis.connection.ReactiveRedisConnectionFactory;
import org.springframework.data.redis.connection.RedisClusterConfiguration;
import org.springframework.data.redis.connection.RedisPassword;
import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettucePoolingClientConfiguration;
import org.springframework.data.redis.core.ReactiveRedisTemplate;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.*;
import org.springframework.util.StringUtils;

import java.net.URI;
import java.net.URISyntaxException;
import java.util.List;

/**
 * @author ： Eidos
 * @date ：Created in 2020/12/29
 * @description：
 * @modified By：
 * @version: 1.0
 */
@ComponentScan({"cn.eidos.blogs.openapi.config"})
@ConfigurationProperties(
        prefix = "spring"
)
@Configuration
public class RedisConfig<K, V> {
    @Autowired
    private RedisProperties redis;
    private final RedisProperties redis1 = new RedisProperties();
    private final RedisProperties redis2 = new RedisProperties();
    private final RedisProperties redis3 = new RedisProperties();
    private final RedisProperties redis4 = new RedisProperties();
    @Autowired
    private RedisTemplate<K, V> redisTemplate;
    @Autowired
    private ReactiveRedisConnectionFactory connectionFactory;

    public RedisConfig() {
    }

    @Lazy
    @Bean(
            name = {"redissonClient"},
            destroyMethod = "shutdown"
    )
    public RedissonClient redissonClient() {
        Config config = new Config();
        List<String> nodes = this.redis.getCluster().getNodes();
        if (nodes != null && nodes.size() > 0) {
            int len = nodes.size();
            String[] addresses = new String[len];

            for (int i = 0; i < len; ++i) {
                addresses[i] = "redis://" + (String) nodes.get(i);
            }

            ((ClusterServersConfig) ((ClusterServersConfig) ((ClusterServersConfig) ((ClusterServersConfig) ((ClusterServersConfig) config.useClusterServers().setConnectTimeout(30000)).setRetryInterval(2000)).setRetryAttempts(10)).setTimeout(30000)).setKeepAlive(true)).addNodeAddress(addresses);
        } else {
            String address = String.format("redis://%s:%s", this.redis.getHost(), this.redis.getPort());
            ((SingleServerConfig) ((SingleServerConfig) ((SingleServerConfig) ((SingleServerConfig) ((SingleServerConfig) config.useSingleServer().setConnectTimeout(30000)).setRetryInterval(2000)).setRetryAttempts(10)).setTimeout(30000)).setKeepAlive(true)).setAddress(address);
        }

        return Redisson.create(config);
    }

    @Lazy
    @Bean(
            name = {"redissonReactiveClient"},
            destroyMethod = "shutdown"
    )
    public RedissonReactiveClient redissonReactiveClient() {
        Config config = new Config();
        List<String> nodes = this.redis.getCluster().getNodes();
        if (nodes != null && nodes.size() > 0) {
            int len = nodes.size();
            String[] addresses = new String[len];

            for (int i = 0; i < len; ++i) {
                addresses[i] = "redis://" + (String) nodes.get(i);
            }

            ((ClusterServersConfig) ((ClusterServersConfig) ((ClusterServersConfig) ((ClusterServersConfig) ((ClusterServersConfig) config.useClusterServers().setConnectTimeout(30000)).setRetryInterval(2000)).setRetryAttempts(10)).setTimeout(30000)).setKeepAlive(true)).addNodeAddress(addresses);
        } else {
            String address = String.format("redis://%s:%s", this.redis.getHost(), this.redis.getPort());
            ((SingleServerConfig) ((SingleServerConfig) ((SingleServerConfig) ((SingleServerConfig) ((SingleServerConfig) config.useSingleServer().setConnectTimeout(30000)).setRetryInterval(2000)).setRetryAttempts(10)).setTimeout(30000)).setKeepAlive(true)).setAddress(address);
        }

        return Redisson.createReactive(config);
    }

    @Lazy
    @Bean(
            name = {"redissonRxClient "},
            destroyMethod = "shutdown"
    )
    public RedissonRxClient redissonRxClient() {
        Config config = new Config();
        List<String> nodes = this.redis.getCluster().getNodes();
        if (nodes != null && nodes.size() > 0) {
            int len = nodes.size();
            String[] addresses = new String[len];

            for (int i = 0; i < len; ++i) {
                addresses[i] = "redis://" + (String) nodes.get(i);
            }

            ((ClusterServersConfig) ((ClusterServersConfig) ((ClusterServersConfig) ((ClusterServersConfig) ((ClusterServersConfig) config.useClusterServers().setConnectTimeout(30000)).setRetryInterval(2000)).setRetryAttempts(10)).setTimeout(30000)).setKeepAlive(true)).addNodeAddress(addresses);
        } else {
            String address = String.format("redis://%s:%s", this.redis.getHost(), this.redis.getPort());
            ((SingleServerConfig) ((SingleServerConfig) ((SingleServerConfig) ((SingleServerConfig) ((SingleServerConfig) config.useSingleServer().setConnectTimeout(30000)).setRetryInterval(2000)).setRetryAttempts(10)).setTimeout(30000)).setKeepAlive(true)).setAddress(address);
        }

        return Redisson.createRx(config);
    }

    @Bean(
            name = {"redisTemplate"}
    )
    public RedisTemplate<K, V> getRedisTemplate() {
        StringRedisSerializer redisSerializer = new StringRedisSerializer();
        this.redisTemplate.setKeySerializer(redisSerializer);
        this.redisTemplate.setHashKeySerializer(redisSerializer);
        this.redisTemplate.setValueSerializer(new GenericToStringSerializer(Object.class));
        this.redisTemplate.setHashValueSerializer(redisSerializer);
        return this.redisTemplate;
    }


    @Bean(
            name = {"redisTemplate1"}
    )
    @Conditional({RedisConfig.EnableRedisTemplate1.class})
    public RedisTemplate<K, V> getRedisTemplate1() {
        RedisTemplate<K, V> template = new RedisTemplate();
        template.setConnectionFactory(this.getLettuceConnectionFactory(this.getRedis1()));
        RedisSerializer<String> redisSerializer = new StringRedisSerializer();
        template.setKeySerializer(redisSerializer);
        template.setHashKeySerializer(redisSerializer);
        template.setValueSerializer(redisSerializer);
        template.setHashValueSerializer(redisSerializer);
        return template;
    }

    @Bean(
            name = {"redisTemplate2"}
    )
    @Conditional({RedisConfig.EnableRedisTemplate2.class})
    public RedisTemplate<K, V> getRedisTemplate2() {
        RedisTemplate<K, V> template = new RedisTemplate();
        template.setConnectionFactory(this.getLettuceConnectionFactory(this.getRedis2()));
        RedisSerializer<String> redisSerializer = new StringRedisSerializer();
        template.setKeySerializer(redisSerializer);
        template.setHashKeySerializer(redisSerializer);
        template.setValueSerializer(redisSerializer);
        template.setHashValueSerializer(redisSerializer);
        return template;
    }

    @Bean(
            name = {"redisTemplate3"}
    )
    @Conditional({RedisConfig.EnableRedisTemplate3.class})
    public RedisTemplate<K, V> getRedisTemplate3() {
        RedisTemplate<K, V> template = new RedisTemplate();
        template.setConnectionFactory(this.getLettuceConnectionFactory(this.getRedis3()));
        RedisSerializer<String> redisSerializer = new StringRedisSerializer();
        template.setKeySerializer(redisSerializer);
        template.setHashKeySerializer(redisSerializer);
        template.setValueSerializer(redisSerializer);
        template.setHashValueSerializer(redisSerializer);
        return template;
    }

    @Bean(
            name = {"redisTemplate4"}
    )
    @Conditional({RedisConfig.EnableRedisTemplate4.class})
    public RedisTemplate<K, V> getRedisTemplate4() {
        RedisTemplate<K, V> template = new RedisTemplate();
        template.setConnectionFactory(this.getLettuceConnectionFactory(this.getRedis4()));
        RedisSerializer<String> redisSerializer = new StringRedisSerializer();
        template.setKeySerializer(redisSerializer);
        template.setHashKeySerializer(redisSerializer);
        template.setValueSerializer(redisSerializer);
        template.setHashValueSerializer(redisSerializer);
        return template;
    }

    @Lazy
    @Bean(
            name = {"reactiveRedisTemplate"}
    )
    public ReactiveRedisTemplate<String, String> getReactiveRedisTemplate() {
        ReactiveRedisTemplate<String, String> reactiveRedisTemplate = new ReactiveRedisTemplate(this.connectionFactory, RedisSerializationContext.string());
        return reactiveRedisTemplate;
    }

    @Lazy
    @Bean(
            name = {"reactiveRedisTemplate1"}
    )
    @Conditional({RedisConfig.EnableRedisTemplate1.class})
    public ReactiveRedisTemplate<String, String> getReactiveRedisTemplate1() {
        ReactiveRedisTemplate<String, String> reactiveRedisTemplate = new ReactiveRedisTemplate(this.getLettuceConnectionFactory(this.getRedis1()), RedisSerializationContext.string());
        return reactiveRedisTemplate;
    }

    @Lazy
    @Bean(
            name = {"reactiveRedisTemplate2"}
    )
    @Conditional({RedisConfig.EnableRedisTemplate2.class})
    public ReactiveRedisTemplate<String, String> getReactiveRedisTemplate2() {
        ReactiveRedisTemplate<String, String> reactiveRedisTemplate = new ReactiveRedisTemplate(this.getLettuceConnectionFactory(this.getRedis2()), RedisSerializationContext.string());
        return reactiveRedisTemplate;
    }

    @Lazy
    @Bean(
            name = {"reactiveRedisTemplate3"}
    )
    @Conditional({RedisConfig.EnableRedisTemplate3.class})
    public ReactiveRedisTemplate<String, String> getReactiveRedisTemplate3() {
        ReactiveRedisTemplate<String, String> reactiveRedisTemplate = new ReactiveRedisTemplate(this.getLettuceConnectionFactory(this.getRedis3()), RedisSerializationContext.string());
        return reactiveRedisTemplate;
    }

    @Lazy
    @Bean(
            name = {"reactiveRedisTemplate4"}
    )
    @Conditional({RedisConfig.EnableRedisTemplate4.class})
    public ReactiveRedisTemplate<String, String> getReactiveRedisTemplate4() {
        ReactiveRedisTemplate<String, String> reactiveRedisTemplate = new ReactiveRedisTemplate(this.getLettuceConnectionFactory(this.getRedis4()), RedisSerializationContext.string());
        return reactiveRedisTemplate;
    }

    private LettuceConnectionFactory getLettuceConnectionFactory(RedisProperties p) {
        RedisProperties.Cluster cluster = p.getCluster();
        RedisClusterConfiguration conf = new RedisClusterConfiguration(cluster.getNodes());
        if (cluster.getMaxRedirects() != null) {
            conf.setMaxRedirects(cluster.getMaxRedirects());
        }

        if (p.getPassword() != null) {
            conf.setPassword(RedisPassword.of(p.getPassword()));
        }

        LettuceClientConfiguration client = this.getLettuceClientConfiguration(p, DefaultClientResources.create(), p.getLettuce().getPool());
        LettuceConnectionFactory f = new LettuceConnectionFactory(conf, client);
        f.afterPropertiesSet();
        return f;
    }

    private LettuceClientConfiguration getLettuceClientConfiguration(RedisProperties p, ClientResources c, RedisProperties.Pool pool) {
        LettuceClientConfiguration.LettuceClientConfigurationBuilder builder = this.createBuilder(pool);
        this.applyProperties(p, builder);
        if (StringUtils.hasText(p.getUrl())) {
            this.customizeConfigurationFromUrl(p, builder);
        }

        builder.clientResources(c);
        return builder.build();
    }

    private LettuceClientConfiguration.LettuceClientConfigurationBuilder createBuilder(RedisProperties.Pool pool) {
        return pool == null ? LettuceClientConfiguration.builder() : (new RedisConfig.PoolBuilderFactory()).createBuilder(pool);
    }

    private LettuceClientConfiguration.LettuceClientConfigurationBuilder applyProperties(RedisProperties p, LettuceClientConfiguration.LettuceClientConfigurationBuilder builder) {
        if (p.isSsl()) {
            builder.useSsl();
        }

        if (p.getTimeout() != null) {
            builder.commandTimeout(p.getTimeout());
        }

        if (p.getLettuce() != null) {
            RedisProperties.Lettuce lettuce = p.getLettuce();
            if (lettuce.getShutdownTimeout() != null && !lettuce.getShutdownTimeout().isZero()) {
                builder.shutdownTimeout(lettuce.getShutdownTimeout());
            }
        }

        return builder;
    }

    private void customizeConfigurationFromUrl(RedisProperties p, LettuceClientConfiguration.LettuceClientConfigurationBuilder builder) {
        RedisConfig.ConnectionInfo connectionInfo = this.parseUrl(p.getUrl());
        if (connectionInfo.isUseSsl()) {
            builder.useSsl();
        }

    }

    protected RedisConfig.ConnectionInfo parseUrl(String url) {
        try {
            URI uri = new URI(url);
            boolean useSsl = url.startsWith("rediss://");
            String password = null;
            if (uri.getUserInfo() != null) {
                password = uri.getUserInfo();
                int index = password.indexOf(58);
                if (index >= 0) {
                    password = password.substring(index + 1);
                }
            }

            return new RedisConfig.ConnectionInfo(uri, useSsl, password);
        } catch (URISyntaxException var6) {
            throw new IllegalArgumentException("Malformed url '" + url + "'", var6);
        }
    }

    public RedisProperties getRedis1() {
        return this.redis1;
    }

    public RedisProperties getRedis2() {
        return this.redis2;
    }

    public RedisProperties getRedis3() {
        return this.redis3;
    }

    public RedisProperties getRedis4() {
        return this.redis4;
    }

    protected static class ConnectionInfo {
        private final URI uri;
        private final boolean useSsl;
        private final String password;

        public ConnectionInfo(URI uri, boolean useSsl, String password) {
            this.uri = uri;
            this.useSsl = useSsl;
            this.password = password;
        }

        public boolean isUseSsl() {
            return this.useSsl;
        }

        public String getHostName() {
            return this.uri.getHost();
        }

        public int getPort() {
            return this.uri.getPort();
        }

        public String getPassword() {
            return this.password;
        }
    }

    private static class PoolBuilderFactory {
        private PoolBuilderFactory() {
        }

        public LettuceClientConfiguration.LettuceClientConfigurationBuilder createBuilder(RedisProperties.Pool properties) {
            return LettucePoolingClientConfiguration.builder().poolConfig(this.getPoolConfig(properties));
        }

        private GenericObjectPoolConfig<?> getPoolConfig(RedisProperties.Pool properties) {
            GenericObjectPoolConfig<?> config = new GenericObjectPoolConfig();
            config.setMaxTotal(properties.getMaxActive());
            config.setMaxIdle(properties.getMaxIdle());
            config.setMinIdle(properties.getMinIdle());
            if (properties.getMaxWait() != null) {
                config.setMaxWaitMillis(properties.getMaxWait().toMillis());
            }

            return config;
        }
    }

    static class EnableRedisTemplate4 implements Condition {
        EnableRedisTemplate4() {
        }

        public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
            Environment env = context.getEnvironment();
            return env.containsProperty("spring.redis4.cluster.nodes");
        }
    }

    static class EnableRedisTemplate3 implements Condition {
        EnableRedisTemplate3() {
        }

        public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
            Environment env = context.getEnvironment();
            return env.containsProperty("spring.redis3.cluster.nodes");
        }
    }

    static class EnableRedisTemplate2 implements Condition {
        EnableRedisTemplate2() {
        }

        public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
            Environment env = context.getEnvironment();
            return env.containsProperty("spring.redis2.cluster.nodes");
        }
    }

    static class EnableRedisTemplate1 implements Condition {
        EnableRedisTemplate1() {
        }

        public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
            Environment env = context.getEnvironment();
            return env.containsProperty("spring.redis1.cluster.nodes");
        }
    }
}
